<!--
 树形结构数据展示
 @Author: mosowe
 @Date:2023-05-11 11:17:58
-->
<template>
	<view class="" v-if="treeData.length">
		<view class="mosowe-tree-list-component" v-for="(item, index) in treeData" :key="index">
			<view class="mosowe-tree-title-wrap">
				<view class="mosowe-tree-title-icon">
					<view class="icon" v-if="item[children] && item[children].length" :class="{
              open: showChidren.includes(item[nodeKey]),
              close: !showChidren.includes(item[nodeKey])
            }" @click="toggleChildrens(item[nodeKey])"></view>
				</view>
				<view class="mosowe-tree-title-selection" :class="{
            disabled: item.disabled,
            'part-select':
              item[children] &&
              treeToArray(JSON.parse(JSON.stringify(item[children])), children).filter((ci) =>
                selectionList.includes(ci[nodeKey])
              ).length > 0 &&
              !selectionList.includes(item[nodeKey]),
            'all-select': selectionList.includes(item[nodeKey])
          }" v-if="multiple" @click="selectionClick(item)"></view>
				<view class="mosowe-tree-title-name" @click.stop="nodeClick(item)">
					<!-- #ifndef MP -->
					<slot :node="item">
						<!-- {{ item[text] }} -->
						<text v-html="item[text]"></text>
					</slot>
					<!-- #endif -->
					<!-- #ifdef MP -->
					<!-- 小程序不支持此类插槽，需要自行处理相关结构在此处 -->
					{{ item[text] }}
					<!-- #endif -->
				</view>
			</view>
			<template v-if="item[children] && item[children].length">
				<template v-if="showChidren.indexOf(item[nodeKey]) > -1">
					<view class="mosowe-tree-children-wrap">
						<mosowe-tree-list v-model="selectionList" :treeData="item[children]" :text="text"
							:children="children" :multiple="multiple" :nodeKey="nodeKey"
							:defaultShowChildren="defaultShowChildren" :textClickToggle="textClickToggle"
							:accordion="accordion" :index="index + 1" :isOnceShow="onceShow" @nodeClick="nodeClick"
							@childCancel="childCancel(item)" @childChoosed="childChoosed(item)">
							<!-- #ifndef MP -->
							<template #default="{ node }">
								<slot :node="node">
									<text v-html="node[text]"></text>
								</slot>
							</template>
							<!-- #endif -->
						</mosowe-tree-list>
					</view>
				</template>
			</template>
		</view>
	</view>
</template>

<script>
	export default {
		name: 'mosowe-tree-list',
		emits: ['nodeClick', 'childCancel', 'childChoosed', 'update:modelValue'],
		props: {
			modelValue: {
				type: Array,
				default: () => []
			},
			value: {
				type: Array,
				default: () => []
			},
			treeData: {
				// 数据
				type: Array,
				default: () => []
			},
			text: {
				// 显示的文案标题字段
				type: String,
				default: 'text'
			},
			nodeKey: {
				// 目标字段
				type: String,
				default: 'value'
			},
			children: {
				// 子集字段
				type: String,
				default: 'childrens'
			},
			multiple: {
				// 显示多选框
				type: Boolean,
				default: false
			},
			textClickToggle: {
				// 标题点击触发子集列表显隐
				type: Boolean,
				default: false
			},
			defaultShowChildren: {
				// 默认展开所有，或展开几级
				type: [Boolean, Number],
				default: false
			},
			accordion: {
				// 手风琴模式
				type: Boolean,
				default: false
			},
			index: {
				// 当前层级
				type: Number,
				default: 1
			},
			isOnceShow: {
				// 是否显示过了
				type: Boolean,
				default: false
			}
		},
		data() {
			return {
				showChidren: [],
				once: false,
				onceShow: false
			};
		},
		computed: {
			selectionList: {
				get() {
					let list = [];
					// TODO 兼容 vue2
					// #ifdef VUE2
					list = this.value;
					// #endif

					// TODO　兼容　vue3
					// #ifdef VUE3
					list = this.modelValue;
					// #endif
					if (!this.once && list.length !== 0) {
						// 只执行一次，初始化时
						const flatArr = this.getValueByKeyForObject({
								value: list,
								source: this.treeData,
								valueKey: this.nodeKey,
								targetKey: this.children,
								treeDataAll: false,
								treeChildKey: this.children
							})
							.flat(Infinity)
							.filter((item) => item)
							.map((item) => {
								return item[this.nodeKey];
							});

						this.once = true;
						const r = [...list, ...flatArr];
						return [...new Set(r)];
					}

					return list;
				},
				set(value) {
					// TODO 兼容 vue2
					// #ifdef VUE2
					this.$emit('input', value);
					// #endif

					// TODO　兼容　vue3
					// #ifdef VUE3
					this.$emit('update:modelValue', value);
					// #endif
				}
			}
		},
		watch: {
			defaultShowChildren: {
				handler() {
					if (!this.isOnceShow) {
						if (this.defaultShowChildren === true) {
							this.showChidren = this.treeToArray(
								JSON.parse(JSON.stringify(this.treeData)),
								this.children
							).map((item) => item[this.nodeKey]);
						} else if (typeof this.defaultShowChildren === 'number') {
							if (this.index < this.defaultShowChildren) {
								const findIndex = (treeData, index) => {
									let r = [];
									treeData.forEach((item) => {
										r.push(item[this.nodeKey]);
										if (
											index < this.defaultShowChildren &&
											item[this.children] &&
											item[this.children].length
										) {
											r.push(...findIndex(item[this.children], index + 1));
										}
									});
									return r;
								};
								const list = findIndex(JSON.parse(JSON.stringify(this.treeData)), 0);
								this.showChidren = list;
							}
						}
						let t = setTimeout(() => {
							clearTimeout(t);
							t = null;
							this.onceShow = this.index === 1 ? true : this.isOnceShow;
						}, 10);
					}
				},
				immediate: true
			}
		},
		methods: {
			nodeClick(e) {
				this.$emit('nodeClick', e);
				console.log(e);
				if (this.textClickToggle) {
					this.toggleChildrens(e[this.nodeKey]);
				}
			},
			// 选择
			selectionClick(node) {
				const list = [...this.selectionList];
				const value = node[this.nodeKey];
				if (node[this.children] && node[this.children].length) {
					// 此项有子集
					if (list.includes(value)) {
						// 已选->改取消选择
						const index = list.findIndex((item) => item === value);
						list.splice(index, 1);
						// 子集的也要取消掉
						const childrenData = JSON.parse(JSON.stringify(node[this.children]));
						this.treeToArray(childrenData, this.children).forEach((item) => {
							const findex = list.findIndex((fitem) => fitem === item[this.nodeKey]);
							list.splice(findex, 1);
						});
						// 父级也要取消
						this.$emit('childCancel');
					} else {
						// 加入已选
						list.push(value);
						// 子集的也要全部加入
						const childrenData = JSON.parse(JSON.stringify(node[this.children]));
						this.treeToArray(childrenData, this.children).forEach((item) => {
							list.push(item[this.nodeKey]);
						});
						// 父级看情况选择不选择
						this.$emit('childChoosed');
					}
				} else {
					// 没有子集
					if (list.includes(value)) {
						// 已选->改取消选择
						const index = list.findIndex((item) => item === value);
						list.splice(index, 1);
						// 父级也要取消
						this.$emit('childCancel');
					} else {
						// 加入已选
						list.push(value);
						// 父级看情况选择不选择
						this.$emit('childChoosed');
					}
				}

				this.selectionList = [...new Set(list)];
			},
			childChoosed(node) {
				let t = setTimeout(() => {
					clearTimeout(t);
					t = null;
					const list = [...this.selectionList];
					const value = node[this.nodeKey];
					let join = true;
					node[this.children].forEach((item) => {
						if (!list.includes(item[this.nodeKey])) {
							join = false;
						}
					});
					if (join) {
						list.push(value);
					}
					// 父级也要考虑下选择不
					this.selectionList = [...new Set(list)];
					this.$emit('childChoosed');
				}, 10);
			},
			childCancel(node) {
				let t = setTimeout(() => {
					clearTimeout(t);
					t = null;
					const list = [...this.selectionList];
					const value = node[this.nodeKey];
					const index = list.findIndex((item) => item === value);
					if (index > -1) {
						list.splice(index, 1);
						this.selectionList = [...new Set(list)];
					} // 父级也要取消
					this.$emit('childCancel');
				}, 10);
			},
			// 展开收起
			toggleChildrens(key) {
				if (this.showChidren.includes(key)) {
					const index = this.showChidren.findIndex((item) => item === key);
					this.showChidren.splice(index, 1);
				} else {
					if (this.accordion) {
						this.showChidren = [key];
					} else {
						this.showChidren.push(key);
					}
				}
			},
			// 树转数组
			treeToArray(nodes, childKey) {
				var r = [];
				if (Array.isArray(nodes)) {
					for (var i = 0, l = nodes.length; i < l; i++) {
						r.push(nodes[i]); // 取每项数据放入一个新数组
						if (Array.isArray(nodes[i][childKey]) && nodes[i][childKey].length > 0)
							// 若存在leaf则递归调用，把数据拼接到新数组中，并且删除该leaf
							r = r.concat(this.treeToArray(nodes[i][childKey]));
						delete nodes[i][childKey];
					}
				}
				return r;
			},
			getValueByKeyForObject(options) {
				if (!options) {
					return [];
				}
				const {
					value,
					source,
					valueKey,
					targetKey,
					treeDataAll = false,
					treeChildKey = 'leaf'
				} = options;
				let treePrefix = '';
				let result = [];
				if (!(value ?? '') || !source || !valueKey || !targetKey) {
					requiredKey(options, ['value', 'source', 'valueKey', 'targetKey']);
					return [];
				}
				let val = value;
				if (typeof value === 'string') {
					val = [value];
				}
				// 树数据处理
				const treeDeal = (data) => {
					if (data[treeChildKey]) {
						// 是个树
						let r = this.getValueByKeyForObject({
							...options,
							source: data[treeChildKey]
						});
						if (treeDataAll && r.length) {
							// 需要返回父级数据
							treePrefix = data[targetKey];
							r = r.map((item) => {
								return treePrefix + '/' + item;
							});
						}
						result.push(...r);
					}
				};
				if (Object.prototype.toString.call(source) === '[object Object]') {
					// object类型，假设是树
					if (val.includes(source[valueKey])) {
						result.push(source[targetKey]);
					}
					treeDeal(source);
				} else {
					// 数组，也可能是数组树
					source.forEach((item) => {
						if (Object.prototype.toString.call(item) === '[object Object]') {
							// object类型，假设是树
							if (val.includes(item[valueKey])) {
								result.push(item[targetKey]);
							}
							treeDeal(item); // 树处理
						}
						if (Array.isArray(item)) {
							// 多维数组
							result.push(
								...this.getValueByKeyForObject({
									...options,
									source: item
								})
							);
						}
					});
				}
				return result;
			},
			requiredKey(obj, keys) {
				let r = [];
				keys.forEach((item) => {
					if (ZoIsEmpty(obj[item])) {
						console.error(`${item} is required!`);
						r.push(item);
					}
				});
				return r;
			}
		}
	};
</script>

<style scoped lang="scss">
	.mosowe-tree-list-component {
		font-size: 14px;
		color: #333;
		line-height: 24px;

		.mosowe-tree-title-wrap {
			display: flex;
			align-items: center;

			.mosowe-tree-title-icon {
				flex: none;
				width: 20px;
				height: 20px;

				.icon {
					width: 100%;
					height: 100%;
					display: flex;
					position: relative;
					cursor: pointer;
					transition: all 0.3s;

					&::before {
						content: '';
						display: inline-block;
						width: 0;
						height: 0;
						border: 6px solid;
						border-color: transparent transparent transparent #999;
						margin: auto;
						margin-right: -2rpx;
					}

					&.open {
						transform: rotate(90deg);
						transform-origin: center;
					}
				}
			}

			.mosowe-tree-title-selection {
				flex: none;
				width: 12px;
				height: 12px;
				border: 1px solid #ccc;
				border-radius: 2px;
				margin-right: 4px;
				cursor: pointer;
				position: relative;

				&.part-select {
					background-color: $uni-color-primary;
					border-color: $uni-color-primary;

					&::after {
						content: '';
						display: block;
						width: 8px;
						height: 2px;
						background-color: #fff;
						border-radius: 2px;
						position: absolute;
						left: 50%;
						top: 50%;
						transform: translateX(-50%) translateY(-50%);
					}
				}

				&.all-select {
					background-color: $uni-color-primary;
					border-color: $uni-color-primary;

					&::after {
						content: '';
						display: block;
						width: 6px;
						height: 3px;
						border: 2px solid;
						border-color: transparent transparent #fff #fff;
						border-radius: 2px;
						position: absolute;
						left: 50%;
						top: 50%;
						transform: translateX(-50%) translateY(-80%) rotate(-45deg);
					}
				}

				&.disabled {
					background-color: rgba(0, 0, 0, 0.2);
					cursor: no-drop;
				}
			}

			.mosowe-tree-title-name {
				flex: 1;
				cursor: pointer;
			}
		}

		.mosowe-tree-children-wrap {
			padding-left: 10px;
		}
	}
</style>